﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Web;
using System.Web.Hosting;
using Cfg.Attributes;

namespace Cfg.ConfigValues
{
    public class PathInfo : ConfigValueBase
	{
        private static string _rootPath;

        public static string BaseUrl
        {
            get
            {
                var httpContext = HttpContext.Current;
                if (httpContext == null)
                {
                    return null;
                }

                if (httpContext.Items.Contains("Cfg.BaseUrl"))
                {
                    var baseUrl = httpContext.Items["Cfg.BaseUrl"] as string;
                    if (!string.IsNullOrEmpty(baseUrl))
                    {
                        return baseUrl.Replace('\\', '/');
                    }
                }

                var uri = httpContext.Request.Url;

                var port = (uri.Port == 80 && uri.Scheme == "http") || (uri.Port == 443 && uri.Scheme == "https")
                    ? string.Empty 
                    : ":" + uri.Port;

                var segments = new[] { uri.Host + port, httpContext.Request.ApplicationPath };
                var url = string.Join("/", segments.Where(x => !string.IsNullOrEmpty(x)).Select(x => x.Trim('/', '\\'))) + "/";

                return string.Format("{0}://{1}", uri.Scheme, url);
            }
            set
            {
                var httpContext = HttpContext.Current;
                if (httpContext == null)
                {
                    return;
                }
                httpContext.Items["Cfg.BaseUrl"] = value;
            }
        }

		/// <summary>
		/// Resolved absolute path
		/// </summary>
		public string FullPath { get; private set; }

		/// <summary>
		/// Clean relative path
		/// </summary>
		public string RelativePath { get; private set; }

        public string RelativeUrl { get; private set; }

        /// <summary>
        /// Value is set in web environment
        /// </summary>
        public string Url
        {
            get
            {
                if (BaseUrl == null || RelativeUrl == null) 
                    return null;

                return BaseUrl.TrimEnd('/') + "/" +  RelativeUrl.TrimStart('/');
            }
        }

        public override void Parse()
        {
			string val = FinalizedStringValue == null
                ? null
                : FinalizedStringValue.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);

            var createIfNotExist = true;
            var throwIfNotExists = false;
            var isFilePath = false;

            var pathAttr = Attributes.OfType<PathAttribute>().FirstOrDefault();
            if (pathAttr != null)
            {
                createIfNotExist = pathAttr.CreateIfNotExists;
                throwIfNotExists = pathAttr.ThrowIfNotExists;
                isFilePath = pathAttr.IsFilePath;
            }

			FullPath = ResolvePath(val, createIfNotExist, throwIfNotExists, isFilePath);

			RelativePath = GetRelativePath(val);

            if (RelativePath == null || RelativePath.StartsWith(".."))
                return;

            RelativeUrl = string.Format("/{0}", RelativePath.Replace(Path.DirectorySeparatorChar, '/'));
            /*
            var httpContext = HttpContext.Current;
            if (httpContext != null)
            {
                var uri = httpContext.Request.Url;
                var port = uri.Port == 80 ? string.Empty : ":" + uri.Port;

                Url = string.Format("{0}://{1}{2}{3}{4}", uri.Scheme, uri.Host, port, httpContext.Request.ApplicationPath, RelativePath);
            }
            */
        }

        /// <summary>
        /// Get root path in current environment
        /// </summary>
        /// <returns></returns>
        public string GetRootPath()
        {
            if (_rootPath != null)
                return _rootPath;

            // for web
            _rootPath = HostingEnvironment.ApplicationPhysicalPath;
            if (_rootPath == null)
            {
                // for win service / application
                _rootPath = AppDomain.CurrentDomain.BaseDirectory.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar;
            }

            return _rootPath;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public string GetRelativePath(string path)
        {
            if (path == null)
                return null;

            path = path.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);

            Uri fromUri = new Uri(GetRootPath());
            Uri toUri = new Uri(ResolvePath(path, false, false, false));

            Uri relativeUri = fromUri.MakeRelativeUri(toUri);
            String relativePath = Uri.UnescapeDataString(relativeUri.ToString());

            return relativePath.Replace('/', Path.DirectorySeparatorChar);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="path"></param>
        /// <param name="createIfNotExists"></param>
        /// <param name="throwIfNotExists"></param>
        /// <param name="isFilePath"></param>
        /// <returns></returns>
        public string ResolvePath(string path, bool createIfNotExists, bool throwIfNotExists, bool isFilePath)
        {
            if (string.IsNullOrEmpty(path))
                return null;

            if (path.StartsWith("~/"))
                path = path.Substring(2);

            if (!Path.IsPathRooted(path))
            {
                path = Path.Combine(GetRootPath(), path);
            }

            var segments = path.Split(Path.DirectorySeparatorChar);
            var stack = new Stack<string>();
            foreach (var segment in segments)
            {
                if (segment == "..")
                {
                    stack.Pop();
                }
                else
                {
                    stack.Push(segment);
                }
            }
            path = string.Join(Path.DirectorySeparatorChar.ToString(CultureInfo.InvariantCulture), stack.Reverse());

            if (isFilePath)
            {
                if (!File.Exists(path))
                {
                    if (createIfNotExists)
                    {
                        var dir = Path.GetDirectoryName(path);
                        if (dir != null)
                        {
                            Directory.CreateDirectory(dir);
                        }
                        using (File.Create(path))
                        { }
                    }
                    else if (throwIfNotExists)
                    {
                        throw new FileNotFoundException("File is missing!", path);
                    }
                }
            }
            else
            {
                if (!Directory.Exists(path))
                {
                    if (createIfNotExists)
                    {
                        Directory.CreateDirectory(path);
                    }
                    else if (throwIfNotExists)
                    {
                        throw new DirectoryNotFoundException("Directory '" + path + "' is missing!");
                    }
                }
            }

            return path;
        }
	}
}
